﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Vortex.Wrapper.Platform;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;
using Vortex.Debugging;

namespace Vortex.Drawing {
	
	/// <summary>
	/// Drawing context represents instance of back-buffer which can be used as render target and copied onto target surface
	/// </summary>
	public class DrawingContext : Resource, IDrawingTarget {
		protected Direct3DSwapChain9 _swapChain;
		protected Surface _backbuffer;
		protected Direct3DSurface9 _depthStencil;
		protected Size _size;

		#region Internal Methods

		protected DrawingContext() {
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DrawingContext"/> class.
		/// </summary>
		/// <param name="windowHandle">The window handle.</param>
		/// <param name="size">The initial size.</param>
		/// <param name="hasDepthBuffer">if set to <c>true</c> then drawing context with have own depth buffer.</param>
		protected internal DrawingContext(IntPtr windowHandle, Size size, bool hasDepthBuffer) {
			WindowHandle = windowHandle;
			HasDepthBuffer = hasDepthBuffer;
			SetSize(size);

			Initialize();

			Canvas = new Canvas2D(this, Device.Renderer);
		}

		protected void SetSize(int width, int height) {
			SetSize(new Size(width, height));
		}

		/// <summary>
		/// Safely sets the size of drawing context.
		/// </summary>
		/// <param name="size">The new size.</param>
		protected void SetSize(Size size) {
			if (size.IsEmpty) {
				size = WinApi.GetWindowClientRect(WindowHandle).Size;
			}

			size.Width = Math.Max(1, size.Width);
			size.Height = Math.Max(1, size.Height);

			_size = size;
		}

		/// <summary>
		/// Initialized this instance.
		/// </summary>
		protected virtual void Initialize() {
			//create swap chain for this context based on parameters 
			CreateSwapChain();

			//depth buffer is optional
			if (HasDepthBuffer) {
				CreateDepthBuffer();
			}

			RaiseCreateSurfaceEvent();
		}

		/// <summary>
		/// Creates the swap chain object for this context
		/// </summary>
		private void CreateSwapChain() {
			PresentParameters presentParameters;
			PreparePresentParameters(out presentParameters);

			HResult result = Device.ApiRef.CreateAdditionalSwapChain(ref presentParameters, out _swapChain);
			Watcher.AssertCritical(result, "Create additional swap chain for {0}", this);

			Direct3DSurface9 backbufferSurface;
			result = _swapChain.GetBackBuffer(0, out backbufferSurface);
			Watcher.AssertCritical(result, "Retrieve backbuffer in {0}", this);
			_backbuffer = new Surface(Device, backbufferSurface);		
		}

		/// <summary>
		/// Creates the depth buffer for this context.
		/// </summary>
		protected void CreateDepthBuffer() {
			HResult result = Device.ApiRef.CreateDepthStencilSurface(
				(uint)Size.Width,
				(uint)Size.Height,
				D3DFormat.D16,
				D3DMultisampleType.None,
				0,
				true,
				out _depthStencil
			);

			Watcher.AssertCritical(result, "Create depth stencil surface for {0}", this);
		}

		/// <summary>
		/// Resets this drawing context.
		/// </summary>
		protected virtual void Reset() {	
			//free everything
			FreeData();

			//create it again
			Initialize();
		}

		/// <summary>
		/// Raises the create surface event.
		/// </summary>
		protected void RaiseCreateSurfaceEvent() {
			//notify all that drawing target has been restored
			if (null != OnCreateSurface) {
				OnCreateSurface(this);
			}
		}

		/// <summary>
		/// Raises the destroy surface event.
		/// </summary>
		protected void RaiseDestroySurfaceEvent() {
			//notify all that drawing target has been destroyed
			if (null != OnDestroySurface) {
				OnDestroySurface(this);
			}
		}

		/// <summary>
		/// Prepares the present parameters for current context.
		/// </summary>
		/// <param name="presentParameters">The present parameters.</param>
		protected internal virtual void PreparePresentParameters(out PresentParameters presentParameters) {
			presentParameters = new PresentParameters();
			presentParameters.EnableAutoDepthStencil = false;
			presentParameters.AutoDepthStencilFormat = D3DFormat.Unknown;
			presentParameters.Windowed = true;
			presentParameters.SwapEffect = D3DSwapEffect.Copy;
			presentParameters.BackBufferFormat = D3DFormat.Unknown;
			presentParameters.DeviceWindow = WindowHandle;
			presentParameters.PresentationInterval = D3DPresentInterval.Immediate;
			presentParameters.BackBufferWidth = (uint)_size.Width;
			presentParameters.BackBufferHeight = (uint)_size.Height;
			presentParameters.Flags = 1;
		}

		#endregion

		/// <summary>
		/// Gets the backbuffer surface of this drawing context.
		/// </summary>
		/// <value>The backbuffer surface.</value>
		public Surface Backbuffer {
			get { return _backbuffer; }
		}

		/// <summary>
		/// Frees the internal data of drawing context.
		/// </summary>
		protected override void FreeData() {
			if (_depthStencil != null) {
				_depthStencil.Dispose();
				_depthStencil = null;
			}

			_backbuffer.Free();
			_backbuffer = null;

			_swapChain.Dispose();
			_swapChain = null;

			RaiseDestroySurfaceEvent();
		}

		/// <summary>
		/// Invoked before the device is reset.
		/// </summary>
		protected override void BeforeDeviceReset() {
			FreeData();
		}

		/// <summary>
		/// Invoked after the device is reset successfully.
		/// </summary>
		protected override void AfterDeviceReset() {
			Initialize();
		}

		#region Public Interface

		/// <summary>
		/// Gets the window handle of this context.
		/// </summary>
		/// <value>The window handle.</value>
		public IntPtr WindowHandle {
			get; protected set;
		}

		/// <summary>
		/// Gets the canvas targeted on this context.
		/// </summary>
		/// <value>The 2D drawing canvas.</value>
		public Canvas2D Canvas { get; protected set; }

		/// <summary>
		/// Gets a value indicating whether this instance has depth buffer.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance has depth buffer; otherwise, <c>false</c>.
		/// </value>
		public bool HasDepthBuffer { get; protected set; }

		/// <summary>
		/// Gets the format of context backbuffer surface.
		/// </summary>
		/// <value>The pixel format.</value>
		public PixelFormat Format {
			get { return (PixelFormat)_backbuffer.Format; }
		}

		/// <summary>
		/// Gets or sets the width of context surface.
		/// </summary>
		/// <value>The width of context surface.</value>
		public int Width { 
			get { return _size.Width; }
			set { Resize(value, Height); }
		}

		/// <summary>
		/// Gets or sets the height of context surface.
		/// </summary>
		/// <value>The height of context surface.</value>
		public int Height { 
			get { return _size.Height; }
			set { Resize(Width, value); }
		}

		/// <summary>
		/// Gets or sets the size of context surface.
		/// </summary>
		/// <value>The size of context surface.</value>
		public Size Size {
			get { return _size; }
			set { Resize(value); }
		}

		/// <summary>
		/// Resizes context surface to match attached window size.
		/// </summary>
		public void Resize() {
			Resize(Size.Empty);
		}

		/// <summary>
		/// Resizes context surface to specified parameters.
		/// </summary>
		/// <param name="newWidth">The new surface width.</param>
		/// <param name="newHeight">The new surface height.</param>
		public void Resize(int newWidth, int newHeight) {
			Resize(
				new Size(newWidth, newHeight)
			);
		}

		/// <summary>
		/// Resizes context surface to specified parameter.
		/// </summary>
		/// <param name="newSize">The new surface size.</param>
		public void Resize(Size newSize) {
			SetSize(newSize);
			Reset();

			//call event on resize texture
			InvokeSurfaceResizeEvent();
		}


		/// <summary>
		/// Invokes the surface resize event.
		/// </summary>
		protected void InvokeSurfaceResizeEvent() {
			
			if (null != OnResizeSurface) {
				OnResizeSurface(this);
			}
		}

		/// <summary>
		/// Presents content of backbuffer of default window.
		/// </summary>
		public void Present() {
			Present(WindowHandle);
		}

		/// <summary>
		/// Presents content of backbuffer into specified target region of default window.
		/// </summary>
		/// <param name="targetRegion">The target region.</param>
		public void Present(Rectangle targetRegion) {
			Present(WindowHandle, targetRegion);
		}

		/// <summary>
		/// Presents content of source region of backbuffer into specified target region of default window.
		/// </summary>
		/// <param name="targetRegion">The target region.</param>
		/// <param name="sourceRegion">The source region.</param>
		public void Present(Rectangle targetRegion, Rectangle sourceRegion) {
			Present(WindowHandle, targetRegion, sourceRegion);
		}

		/// <summary>
		/// Presents content of backbuffer into specified window.
		/// </summary>
		/// <param name="targetWindowHandle">The target window handle.</param>
		public void Present(IntPtr targetWindowHandle) {
			_swapChain.Present(null, null, targetWindowHandle);
		}

		/// <summary>
		/// Presents content of backbuffer into specified target region of specified window
		/// </summary>
		/// <param name="targetWindowHandle">The target window handle.</param>
		/// <param name="targetRegion">The target region.</param>
		public void Present(IntPtr targetWindowHandle, Rectangle targetRegion) {
			_swapChain.Present(null, targetRegion, targetWindowHandle);
		}

		/// <summary>
		/// Presents content of source region of backbuffer into specified target region of specified window.
		/// </summary>
		/// <param name="targetWindowHandle">The target window handle.</param>
		/// <param name="targetRegion">The target region.</param>
		/// <param name="sourceRegion">The source region.</param>
		public void Present(IntPtr targetWindowHandle, Rectangle targetRegion, Rectangle sourceRegion) {
			_swapChain.Present(sourceRegion, targetRegion, targetWindowHandle);
		}

		#endregion

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public override string ToString() {
			return string.Format("{{DrawingContext: size={0}, hasDepth={1}}}", _size, HasDepthBuffer);
		}

		#region IDrawingTarget Members

		/// <summary>
		/// Gets a value indicating whether drawing context has Z buffer and supports Z-Effects.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this context has Z buffer; otherwise, <c>false</c>.
		/// </value>
		public bool HasZBuffer {
			get { return _depthStencil != null; }
		}


		/// <summary>
		/// Gets the drawing target internal data.
		/// </summary>
		/// <value>The drawing target data.</value>
		public TargetData TargetData {
			get { return new TargetData(null != _backbuffer ? _backbuffer.ApiRef : null, _depthStencil); }
		}

		/// <summary>
		/// Occurs when drawing context surface is created.
		/// </summary>
		public event DrawingTargetEventHandler OnCreateSurface;


		/// <summary>
		/// Occurs when drawing context surface is destroyed.
		/// </summary>
		public event DrawingTargetEventHandler OnDestroySurface;

		/// <summary>
		/// Occurs when drawing target was resized. It is invoke after the new surface is created.
		/// </summary>
		public event DrawingTargetEventHandler OnResizeSurface;

		#endregion
	}
}
